﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental;

namespace TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Functions.Readers
{
    /// <summary>
    /// Reads boolean system from PLA FD format.
    /// </summary>
    public class BooleanSystemReaderPlafd : BooleanSystemReader
    {
        /// <summary>
        /// Input string to read data from.
        /// </summary>
        public string Input { get; set; }

        /// <summary>
        /// Reads BooleanSystem object from text format.
        /// </summary>
        public override void Read()
        {
            if (ReferenceEquals(null, System))
            {
                throw new InvalidOperationException("BooleanSystem object required to read data to.");
            }

            if (Input == String.Empty)
            {
                throw new InvalidOperationException("Input string required.");
            }

            // splitting to lines
            var delimeters = new[] { "\n", "\r\n" };
            var lines = Input.Split(delimeters, Int32.MaxValue, StringSplitOptions.RemoveEmptyEntries);
            if (lines.Length == 0)
            {
                throw new InvalidDataException("Invalid source string: no one line of values.");
            }

            // format attributes pattern
            var attrPattern = new Regex("\\.(type|name|code|mask|i|o|p|e)(\\s(.*))?");

            // parsing lines by pattern
            var pattern = new Regex("([01-]+)\\s+([01-]+)");

            // parsing line by line
            var dimension = 0;
            var funcNumber = 0;
            var functions = new List<BooleanFunction>();
            var productsCount = 0;

            // system attributes
            var inputs = 0;
            var outputs = 0;
            var products = 0;
            foreach (var line in lines)
            {
                if (line == string.Empty) continue;

                // parsing attributes
                var attrMatch = attrPattern.Match(line);
                if (attrMatch.Success)
                {
                    // attribute line
                    var attrName = attrMatch.Groups[1].ToString();
                    var attrValue = attrMatch.Groups[3].ToString();
                    if (attrName == "type" && attrValue.ToLower() != "fd")
                    {
                        throw new InvalidDataException("Supported only FD format");
                    }

                    switch (attrName)
                    {
                        case "i":
                            inputs = Int32.Parse(attrValue);
                            break;
                        case "o":
                            outputs = Int32.Parse(attrValue);
                            break;
                        case "p":
                            products = Int32.Parse(attrValue);
                            break;
                    }

                    continue;
                }

                // parsing system values
                var match = pattern.Match(line);
                if (!match.Success)
                {
                    throw new InvalidDataException("Invalid format for source string.");
                }

                // parsing line
                var vector = match.Groups[1].ToString();
                var funcValues = match.Groups[2].ToString();

                if (inputs != 0 && vector.Length != inputs)
                {
                    throw new InvalidDataException("Invalid format for source string: cubes dimensions must be the same as 'i' attribute.");
                }

                if (outputs != 0 && funcValues.Length != outputs)
                {
                    throw new InvalidDataException("Invalid format for source string: number of functions must be the same as 'o' attribute.");
                }

                // setting dimension of system cubes
                if (dimension == 0)
                {
                    dimension = vector.Length;
                }

                if (vector.Length != dimension)
                {
                    throw new InvalidDataException("Invalid format for source string: different cubes dimensions for one system.");
                }

                // memorizing system dimension
                if (funcNumber == 0)
                {
                    funcNumber = funcValues.Length;

                    // instanciating system functions
                    for (var i = 0; i < funcNumber; i++)
                    {
                        var function = new BooleanFunction();
                        functions.Add(function);
                    }
                }

                if (funcValues.Length != funcNumber)
                {
                    throw new InvalidDataException("Invalid format for source string: different number of functions for one system.");
                }

                // building cube
                Cube cube;
                try
                {
                    cube = new Cube(vector);
                    productsCount++;
                }
                catch (ArgumentException e)
                {
                    throw new InvalidDataException("Invalid format for source string: invalid ternary vector character.", e);
                }

                // adding cube to the system
                for (var i = 0; i < functions.Count; i++)
                {
                    if (funcValues[i] == '1')
                    {
                        functions[i].Domain1.Add(cube);
                    }
                }
            }

            if (products != 0 && productsCount != products)
            {
                throw new InvalidDataException("Invalid format for source string: number of system products must be the same as 'p' attribute.");
            }

            // setting data
            System.Functions = functions;
        }
    }
}
